package com.example.stock.entity.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.stock.comm.StrategyEnum;
import com.example.stock.entity.eo.StockTradeEo;
import com.example.stock.entity.mapper.StockTradeMapper;
import com.example.stock.entity.service.IStockTradeService;
import com.example.stock.service.impl.ServiceBasicImpl;
import com.example.stock.util.CommUtil;
import com.example.stock.vo.EmaTradeVo;
import com.example.stock.vo.ScoreVo;
import com.example.stock.vo.ViewVo;
import com.example.stock.vo.common.TradeVo;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

@Service
public class StockTradeServiceImpl extends ServiceImpl<StockTradeMapper, StockTradeEo> implements IStockTradeService {

    @Resource
    private ServiceBasicImpl basicServiceImpl;
    @Resource
    private StockTradeMapper stockTradeMapper;


    private static final Double CHARGE_RATE = 0.0001;

    public Double doTrade(List<TradeVo> tradeVos, boolean save, String tsCode, StrategyEnum strategyEnum) {

        if (save) {
            remove(Wrappers.<StockTradeEo>lambdaUpdate()
                    .eq(StockTradeEo::getStrategy,strategyEnum.name())
                    .eq(StockTradeEo::getTsCode, tsCode));
        }
        String name = basicServiceImpl.getName(tsCode);

        StockTradeEo tradeEo = new StockTradeEo()
                .setMoney(1000000.0)
                .setHoldStatus((short) 0);

        for (TradeVo tradeVo : tradeVos) {
            Double price = tradeVo.getPrice();
            if (tradeVo.isCanBuy()) {
                if (emptyHold(tradeEo)) {
                    //可用金额(扣除手续费)
                    Double totalM = tradeEo.getMoney() - (tradeEo.getMoney() * CHARGE_RATE);
                    tradeEo.setHoldStatus((short) 1);
                    tradeEo.setHoldTime(tradeVo.getTradeTime());
                    tradeEo.setCharge(tradeEo.getMoney()*CHARGE_RATE);
                    tradeEo.setHoldVol(totalM / price);
                    tradeEo.setMoney(totalM);
                    tradeEo.setPrice(price);
                    tradeEo.setProfit(0.0);

                    saveTradeEo(save, tsCode,name, tradeEo,tradeVo,strategyEnum);
                }

            } else {
                if (!emptyHold(tradeEo)) {
                    //当天进行过交易
                    if (tradeVo.getTradeTime().getDayOfYear() == tradeEo.getHoldTime().getDayOfYear()) {
                        continue;
                    }
                    //金额 = 持仓*当前收盘价*(1-手续费)
                    Double money = tradeEo.getHoldVol() * price * (1.0 - CHARGE_RATE);
                    tradeEo.setProfit(money - tradeEo.getMoney());
                    tradeEo.setMoney(money);
                    tradeEo.setCharge(tradeEo.getHoldVol()*price*CHARGE_RATE);
                    tradeEo.setHoldVol(0.0);
                    tradeEo.setHoldStatus((short) 0);
                    tradeEo.setHoldTime(tradeVo.getTradeTime());
                    tradeEo.setPrice(tradeVo.getPrice());

                    saveTradeEo(save, tsCode,name, tradeEo, tradeVo,strategyEnum);
                }
            }
        }
        return tradeEo.getMoney();
    }


    private void saveTradeEo(boolean save, String tsCode, String name, StockTradeEo tradeEo, TradeVo tradeVo, StrategyEnum strategyEnum) {

        tradeEo.setTsCode(tsCode);
        tradeEo.setName(name);
        tradeEo.setStrategy(strategyEnum.name());

        if (tradeVo instanceof EmaTradeVo) {
            tradeEo.setCalcResult(((EmaTradeVo) tradeVo).getCalc1());
        }
        if (save) {
            save(tradeEo);
        }
    }

    public boolean emptyHold(StockTradeEo hold) {
        return hold.getHoldStatus() == null || hold.getHoldStatus() == 0;
    }



    public List<ViewVo> getViewVo(List<LocalDateTime> listDate, List<StockTradeEo> stockTradeEos) {
        List<ViewVo> ret = new ArrayList<>();
        for (LocalDateTime localDateTime : listDate) {
            ViewVo viewVo = new ViewVo();
            viewVo.setLocalDateTime(localDateTime);

            Optional<StockTradeEo> max = stockTradeEos.stream()
                    .filter(t -> !t.getHoldTime().isAfter(localDateTime))
                    .max(Comparator.comparing(StockTradeEo::getHoldTime));
            if (max.isPresent()) {
                StockTradeEo stockTradeEo = max.get();
                viewVo.setMoney(stockTradeEo.getMoney());
                viewVo.setPrice(stockTradeEo.getPrice());

            }else {
                StockTradeEo stockTradeEo = stockTradeEos.get(0);
                viewVo.setMoney(stockTradeEo.getMoney());
                viewVo.setPrice(stockTradeEo.getPrice());
            }
            ret.add(viewVo);
        }
        return  ret;
    }



    public ScoreVo getScore(List<StockTradeEo> stockTradeEos){
        if (CollectionUtil.isEmpty(stockTradeEos)) {
            return new ScoreVo();
        }
        ScoreVo scoreVo = new ScoreVo();
        scoreVo.setScore(0.0D);

        StockTradeEo firstTrade = stockTradeEos.get(0);
        StockTradeEo lastedTrade = stockTradeEos.get(stockTradeEos.size() - 1);

        scoreVo.setStartTime(firstTrade.getHoldTime());
        scoreVo.setEndTime(lastedTrade.getHoldTime());



        scoreVo.setReturnRateTotalMy(getDouble((lastedTrade.getMoney() - firstTrade.getMoney()) / firstTrade.getMoney() * 100));
        scoreVo.setReturnRateTotalIndex(getDouble((lastedTrade.getPrice() - firstTrade.getPrice()) / firstTrade.getPrice() * 100));
        scoreVo.setReturnRateTotalRelative(scoreVo.getReturnRateTotalMy() - scoreVo.getReturnRateTotalIndex());

        long days = Duration.between(firstTrade.getHoldTime(), lastedTrade.getHoldTime()).toDays();

//        double v = scoreVo.getReturnRateTotalMy() * 365 / days;
        double a = scoreVo.getReturnRateTotalMy() / 100 + 1;
        double years = 365.0 / days;
        double yearReturn = Math.pow(a, years)-1;
        scoreVo.setReturnRateYearMy(days == 0 ? 0 : yearReturn*100);
//        double w = scoreVo.getReturnRateTotalIndex() * 365 / days;
        double w = Math.pow(scoreVo.getReturnRateTotalIndex()/100+1, years)-1;
        scoreVo.setReturnRateYearIndex(days == 0 ? 0 : w*100);
//        double x = Math.pow(scoreVo.getReturnRateTotalRelative()/100+1, years)-1;
        scoreVo.setReturnRateYearRelative((yearReturn - w)*100);
        scoreVo.setReturnRateTotalRelative((yearReturn - w)*100);

        double maxDrawdown = 0.0;
        StockTradeEo temp = firstTrade;
        StockTradeEo startDrawDown = firstTrade;
        StockTradeEo endDrawDown = firstTrade;
        for (StockTradeEo currentTrade : stockTradeEos) {
            double earnMoney = currentTrade.getMoney() - temp.getMoney();
            if (earnMoney >= 0) {
                temp = currentTrade;
                continue;
            }
            double currentDrawDown = (earnMoney / temp.getMoney())*100;
            if (currentDrawDown < maxDrawdown) {
                maxDrawdown = currentDrawDown;
                startDrawDown = temp;
                endDrawDown = currentTrade;
            }

        }

        scoreVo.setDrawdownMe(getDouble(maxDrawdown));
        scoreVo.setDrawdownIndex(getDouble( (endDrawDown.getPrice()-startDrawDown.getPrice())/startDrawDown.getPrice() *100 ));
        scoreVo.setDrawdownStartTime(startDrawDown.getHoldTime());
        scoreVo.setDrawdownEndTime(endDrawDown.getHoldTime());

        CommUtil.score(scoreVo);
        return scoreVo;
    }

    private double getDouble(double v) {
        return new BigDecimal(v).setScale(3, RoundingMode.HALF_UP).doubleValue();
    }
}
